/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * Licensed under Simplified BSD License
 *
 * Buggy
 *
 * @author: jin Chen
 * @date: Jun 6, 2005$
 * @version: 1.0
 */
package edu.psu.geovista.app.hce.dendroview.node;

import edu.psu.geovista.app.hce.clustering.model.BCluster;
import edu.psu.geovista.app.hce.clustering.model.LCluster;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;


public class VerticalBNode implements BNode {
    private static final Logger logger=Logger.getLogger(VerticalBNode.class.getName());
    private static final boolean DEBUG=true;
    //mandatory attributes, independent
    private int orient;
    private int numOfLeaf;
    Rectangle graphBound; //graph size of dendrogram. graphsize+(head&foot)offset = plotsize

    //non-mandatory attributes
    double minSimilarity=Double.NaN ;//mininum similarity of a cluster model
    double maxSimilarity=Double.NaN ; //maximum similarity of a cluster model
    double[] leafsInterval;//  

    //paint different on various similarities
    double similarityThreshold;// 
    Color explicitColor=Color.black ;// see usage in similarityThreshold
    Color implicitColor=Color.gray ;


    java.util.List<Point> leafsLocation;
    boolean showdetail=true;//  
    protected Font labelfond=new Font("Arial",Font.PLAIN,12); //font for draw label for each leaf


    public VerticalBNode() {

    }



    /**
     *
     * @param g2d
     * @param cluster
     * @return       
     */
    public  Point drawCluster(Graphics2D g2d, BCluster cluster){

        Point myloc; // 
        if(! (cluster instanceof LCluster)){//no need to draw leaf cluster
             BCluster left = cluster.getCls1();
             BCluster right = cluster.getCls2();

            Point lb = drawCluster(g2d, left);  //left bottom
            Point rb = drawCluster(g2d, right); //right bottom

            double similarity = cluster.getSimilarity();
           /* float maxdistance=0.005f;//  
            int distance=(int) (graphsize.height*(similarity-maxdistance));*/
            int distance = this.getDistance(similarity);

            if(logger.isLoggable(Level.FINE ) ){

                if (left instanceof LCluster &&right instanceof LCluster) {
                    LCluster leftcls = (LCluster) left;
                    LCluster rightcls = (LCluster) right;

                    int leftIndex = leftcls.getDtIndex();
                    int rightIndex = rightcls.getDtIndex();
                    System.out.println("distance btw:["+leftIndex+","+rightIndex+"] is"+distance+" with sim:"+similarity);
                    if((leftIndex==4&&rightIndex==8)
                      ||(leftIndex==8&&rightIndex==4)){
                        System.out.println("");
                    }


                }
                else if(left instanceof LCluster){
                    LCluster cls = (LCluster) left;
                    int Index = cls.getDtIndex();
                     if(Index==17){
                        System.out.println("distance btw 17 "+distance+" with sim:"+similarity);
                    }
                }
                else if(right instanceof LCluster){
                    LCluster cls = (LCluster) right;
                    int Index = cls.getDtIndex();
                     if(Index==17){
                        System.out.println("distance btw 17 "+distance+" with sim:"+similarity);
                    }
                }

            }

             if(logger.isLoggable(Level.FINE ) ){
                         //System.out.println("draw node:"+cluster.getIndex() );
              }
            Point[] locs=this.getLocations(lb,rb,distance);

            //set paint differenly with different similarity of the node
            Color orgcolor = g2d.getColor();
            if( similarity>=this.getSimilarityThreshold()){
                g2d.setColor(this.explicitColor);
            }
            else{
                g2d.setColor(this.implicitColor );
            }
            //draw a node
            drawInterNode(locs, g2d);
            g2d.setColor(orgcolor);
            myloc=locs[2];
        }
        else{

            LCluster lf=(LCluster) cluster;
            int leafIndex = lf.getLfindex();
            myloc = getLeafLocation(leafIndex);
            if(DEBUG){
                String lb = lf.getId();
                if(lb==null){
                     lb = "id:" + lf.getDtIndex();
                }

                Point lbloc = getLabelLocation(lf, lb, g2d);
               // g2d.drawString(lb ,myloc.x+3,myloc.y);
               g2d.drawString(lb ,lbloc.x,lbloc.y);
            }

        }


        return myloc;

    }

     /**
     *  
     * @param locs    
     * @param g2d
     */
    protected void drawInterNode(Point[] locs, Graphics2D g2d) {
        int size = locs.length;
        int[] xps=new int[size ];
        int[] yps=new int[size];
        for (int i=0;i<yps.length ;i++){
            xps[i]=locs[i].x;
            yps[i]=locs[i].y;
         }
        g2d.drawPolyline(xps,yps,yps.length );
    }

    /*******************************************************************************************************
     *                locations
     *******************************************************************************************************/



    /**************************************************
     *                distance
     ***************************************************/

    protected  int getFullDistance() {
                if(graphBound==null) return 100;
               return Math.abs(graphBound.height-getSpaceForLabel());
    }


    public int getSpaceForLabel(){
        return 0;
    }


    protected int getDistance( double similarity) {

        int fullDistance = getFullDistance(); //
        int distance=0;

        double maxsim = this.getSimilarityForLeaf();//this.getMaxSimilarity();
        double minsim = this.getSimilarityForRoot();//this.getMinSimilarity();
        distance=(int) (fullDistance*Math.abs(similarity-minsim) /Math.abs(maxsim-minsim) );

        //distance=(int) (fullDistance *(similarity-0.5-maxdistance));

        if(logger.isLoggable(Level.FINE ) ){
                     //  System.out.println("getDistance(length,similarity)"+similarity +" is "+distance+ ". Full length:"+fullDistance);
          }
        //int offset=10;

        return distance;
    }


    public  double getSimilarity ( int distance){


       double fullDistance = getFullDistance();
       double leafsim = this.getSimilarityForLeaf();//this.getMaxSimilarity();
       double roofsim = this.getSimilarityForRoot();//this.getMinSimilarity();
        double minsim = this.getMinSimilarity();
        double maxsim = this.getMaxSimilarity();

       double sim=(leafsim -roofsim)*(distance/fullDistance)+roofsim;


       return sim;
    }

    /**
     *
     * @param pt  location on plot
     * @return
     */
    public  double getSimilarity ( Point pt){


       // double minsim = this.getMinSimilarity();
        int mindistance =0 ;//distance of minsim to minsim.this.getDistance(minsim);
        int minsimLoc = this.getLocation(mindistance, this.getOrient(), this.getGraphBound()); //min sim location on plot coordination
        int distance=Math.abs(pt.y-minsimLoc);//this is not good since if it pass the distance=0 point, it will show positive value again
        if(this.getOrient() ==SwingConstants.SOUTH ){
           distance=pt.y-minsimLoc;
         }
         else if(this.getOrient() ==SwingConstants.NORTH ){
           distance=minsimLoc-pt.y;
         }

        return this.getSimilarity(distance);

    }


      /**************************************************
       *                locations
       ***************************************************/


    /**************************************************
     *  location methods, need overriden by subclass
     ***************************************************/
     /**
     *  buggy
     */
    protected void computeDefaultLeafsLocation(){
        if(logger.isLoggable(Level.FINE ) ){
             System.out.println("computeLeafsLocation() -->\n");
        }
         if(this.getGraphBound() ==null){
             if(logger.isLoggable(Level.FINE ) ){
                 System.out.println("  Graph bound is null, unable to calculate, simply return.");
             }
             //logger.fine("  Graph bound is null, unable to calculate, simply return.");
             return;
         }
        int x,y;
        //int similarity=1;// leaf node similairty =1;
        int distance=this.getDistance(getSimilarityForLeaf());//graphBound.height+this.offset.height ;   // 
        //int distance=this.getDistance(this.getMaxSimilarity() );

        Rectangle bd = this.getGraphBound();
        double length = bd.width;
        int numOfLeaf = this.getNumOfLeaf();
        int nodesize=(int) (length/numOfLeaf);



        List <Point> leafsloc=new ArrayList<Point>(numOfLeaf);



        this.setLeafsLocation(leafsloc) ;


    }

    protected Point[] getLocations(Point foot1,Point foot2,int distance){
         Point[] locs=new Point[5];
         int x=(foot1.x+foot2.x)/2;
        int y = getLocation(distance, this.getOrient(), this.getGraphBound());
        Point myloc=new Point();
            myloc.setLocation(x,y); // 

            Point shoulder1=new Point();
            Point shoulder2=new Point();
            shoulder1.setLocation(foot1.x,y);
            shoulder2.setLocation(foot2.x,y);

           // locs[0]=myloc;
        locs=new Point[]{foot1,shoulder1,myloc,shoulder2,foot2};
        return locs;

    }

    /**
     *
     * @param leafIndex
     * @return           location of leaf cluster node
     */
    public   Point getLeafLocation(int leafIndex) {

        if(this.leafsInterval==null){ //no external interval given, need use self compute value
            List<Point> locs = this.getLeafsLocation();
            if (locs==null)return null;
            return locs.get(leafIndex);
        }
        else{
             //int similarity=1;// leaf node similairty =0;
             int distance=this.getDistance(getSimilarityForLeaf());
             int y=this.getLocation(distance, this.getOrient(), this.getGraphBound());
             //int y=this.getDistance(getMaxSimilarity());
             int x=(int) this.leafsInterval[leafIndex];
             return new Point(x,y);
        }
     }
 
    protected  int getLocation(int distance, int orient, Rectangle graphBound) {
        int y=distance;//(int) (plotsize.height*(1- distance));
        if(orient == SwingConstants.SOUTH ){
            y=graphBound.y+distance;
        }
        else if (orient ==SwingConstants.NORTH ){
             y=graphBound.y+graphBound.height -distance;
        }
        else{
            logger.severe("Unsupport oritation");
        }
        return y;
    }

    /**
     *  
     * @param lf
     * @param msg
     * @param g2d
     * @return
     */
      protected Point getLabelLocation( LCluster lf,String msg,Graphics2D g2d){
         int leafIndex = lf.getLfindex();
         Point lflocation = getLeafLocation(leafIndex);


        return lflocation;//new Point(x,y);
    }



     /**************************************************
      *                important values
      ***************************************************/
     protected  final int getSimilarityForLeaf(){
         return 1;//
     }
     protected double getSimilarityForRoot(){
        if(this.isShowdetail() ){
            return this.getMinSimilarity();
        }
        else{
            return 0;//
        }
    }
      public double getMaxSimilarity() {
        if(Double.isNaN(maxSimilarity)){
            return 1;
        }
        return 1;//maxSimilarity;//-0.005; //todo temp, the make highly similar cluster visible
    }
       public double getMinSimilarity() {
         if(Double.isNaN(minSimilarity)){
            return 0;
        }
        return minSimilarity;//+0.005;//todo temp, to make the root visible
    }




    /*******************************************************************************************************
     *                getter setter
     *******************************************************************************************************/

    /**************************************************
     *                important setting
     ***************************************************/
    /**
     * set dendrogram graph size
     * @param graphBound
     */
    public void setGraphBound(Rectangle graphBound) {
        this.graphBound = graphBound;
        computeDefaultLeafsLocation();

    }

    /**
     *
     * @param orient  SwingConstants.SOUTH/NORTH
     */
    public void setOrient(int orient) {
        if(orient!=SwingConstants.SOUTH ||orient!=SwingConstants.NORTH){
           new IllegalArgumentException(this.getClass().getName() +" is passed unknown orientation!");
        }
        this.orient = orient;
        computeDefaultLeafsLocation();
    }
    /**
     * set the interval distance btw leaf nodes. If not set this value, the class will compute by itself
     * @param leafsInterval
     */
    public void setLeafsInterval(double[] leafsInterval) {
        this.leafsInterval = leafsInterval;
    }
      public void setNumOfLeaf(int numOfLeaf) {

        this.numOfLeaf = numOfLeaf;
        if(this.graphBound !=null&&isOrientKnown(this.getOrient() )){
            //isOrientKnown(this.getOrient() is to avoid outprint severe info
            computeDefaultLeafsLocation();
        }
    }

    protected boolean isOrientKnown(int orient){
         if(orient == SwingConstants.SOUTH ||orient ==SwingConstants.NORTH ){
            return true;
        }
        return false;
    }
    /**************************************************
     *                others
     ***************************************************/
    /**
     *
     * @return    graph bound
     */
    public Rectangle getGraphBound() {
        return graphBound;
    }



    /**
     * exist only for keep old class work
     * @return d
     */
    public int getNodesize() {
        return 100;
    }

    public double[] getLeafsInterval() {
        return leafsInterval;
    }



    public int getOrient() {
        return orient;
    }



    public int getNumOfLeaf() {
        return numOfLeaf;

    }



    public List<Point> getLeafsLocation() {
        return leafsLocation;
    }

    public void setLeafsLocation(List<Point> leafsLocation) {
        this.leafsLocation = leafsLocation;
    }



    public void setMaxSimilarity(double maxSimilarity) {
        this.maxSimilarity = maxSimilarity;
    }



    public void setMinSimilarity(double minSimilarity) {
        this.minSimilarity = minSimilarity;
    }

    public boolean isShowdetail() {
        return showdetail;
    }

    public void setShowdetail(boolean showdetail) {
        this.showdetail = showdetail;
       // this.computeDefaultLeafsLocation();
    }

    public double getSimilarityThreshold() {
        return similarityThreshold;
    }

    public void setSimilarityThreshold(double similarityThreshold) {
        this.similarityThreshold = similarityThreshold;
    }

    /*******************************************************************************************************
     *                test
     *******************************************************************************************************/
   private void printArray(int[] a){
        System.out.print("[");
        for (int i=0;i<a.length ;i++){
            System.out.print(a[i]+",");
        }
        System.out.print("]");
        System.out.println("");
    }

}
